package com.coderg.gmall.oauth2.config;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.bootstrap.encrypt.KeyProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.security.KeyPair;


/**
 * 授权服务
 * oauth框架的核心配置，实现三个方法
 */
@Configuration
@EnableAuthorizationServer
class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    /**
     * 用户自定义实现的登录校验流程
     */
    @Autowired
    UserDetailsService userDetailsService;
    /**
     * 认证授权管理器
     */
    @Autowired
    AuthenticationManager authenticationManager;


    /**`
     * 数据源，用于从数据库获取数据进行认证操作
     */
    @Autowired
    private DataSource dataSource;
    //客户端配置
    @Bean
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }
    /***
     * 客户端信息配置，会加载数据表
     *
     * 配置ClientDetailsService，例如声明单个客户端及其属性。
     * 请注意，除非向配置（AuthorizationServerEndpointsConfigurer）提供了AuthenticationManager，
     * 否则不会启用密码授予（即使允许某些客户机这样做）。
     * 必须声明至少一个客户端，或者一个完全形成的自定义ClientDetailsService，否则服务器将无法启动。
     * 形参: 客户端——客户端详细信息配置器
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //以数据源的方式,自动从数据库加载oauth的配置
        clients.jdbc(dataSource).clients(clientDetails());
    }


    /**
     * 令牌持久化存储接口
     */
    @Autowired
    TokenStore tokenStore;

    @Bean
    @Autowired
    public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
        return new JwtTokenStore(jwtAccessTokenConverter);
    }
    /***
     * 授权服务器端点配置
     * 配置Authorization Server端点的非安全特性，如令牌存储、令牌自定义、用户审批和授权类型。
     *      * 默认情况下，您不需要做任何事情，除非您需要授予密码，在这种情况下，您需要提供AuthenticationManager。
     *      * 形参: 端点——端点配置器
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.accessTokenConverter(jwtAccessTokenConverter)//令牌生成
                .authenticationManager(authenticationManager)//认证管理器
                .tokenStore(tokenStore)                       //令牌存储
                .userDetailsService(userDetailsService);     //用户信息service
    }

    /***
     * 授权服务器的安全配置
     *
     * 配置授权服务器的安全性，这实际上意味着/oauth/令牌端点。
     * /oauth/authorize端点也需要是安全的，但这是一个普通的面向用户的端点，应该像UI的其他部分一样受到保护，因此这里不做介绍。
     * 默认设置涵盖了最常见的需求，遵循OAuth2规范的建议，因此您不需要在这里做任何事情即可启动并运行基本服务器。
     * 形参: 安全-一个流畅的安全特性配置器
     * @param oauthServer
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.allowFormAuthenticationForClients()
                .passwordEncoder(new BCryptPasswordEncoder())
                .tokenKeyAccess("permitAll()")//拥有的权限
                .checkTokenAccess("isAuthenticated()");//所有请求都要验证
    }


    //读取密钥的配置
    @Bean("keyProp")
    public KeyProperties keyProperties() {
        return new KeyProperties();
    }

    @Resource(name = "keyProp")
    private KeyProperties keyProperties;

    /**
     * jwt令牌转换器    作用生成令牌  组合了KeyPair 这个类有私钥和公钥
     * 组合DefaultAccessTokenConverter  这个类组合了 CustomUserAuthenticationConverter（可以自定义载荷）
     */
    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    /****
     * JWT令牌转换器   （默认有一个，我们需要自定义一些信息，包装一下它）
     * @param customUserAuthenticationConverter
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(CustomUserAuthenticationConverter customUserAuthenticationConverter) {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        //加载私钥文件，获取私钥,公钥
        KeyPair keyPair = new KeyStoreKeyFactory(
                keyProperties.getKeyStore().getLocation(),
                //库密码
                keyProperties.getKeyStore().getSecret().toCharArray())
                .getKeyPair(
                        keyProperties.getKeyStore().getAlias(),
                        //密码
                        keyProperties.getKeyStore().getPassword().toCharArray());
        converter.setKeyPair(keyPair);
        //配置自定义的CustomUserAuthenticationConverter
        DefaultAccessTokenConverter accessTokenConverter = (DefaultAccessTokenConverter) converter.getAccessTokenConverter();
        accessTokenConverter.setUserTokenConverter(customUserAuthenticationConverter);
        return converter;
    }
}

